package cn.yx.common.security.webmvc.filter;

import cn.hutool.core.util.StrUtil;
import cn.yx.common.core.enums.CharsetEnum;
import cn.yx.common.core.enums.ContentTypeEnum;
import cn.yx.common.core.pojo.ResponseVo;
import cn.yx.common.core.util.StringUtil;
import cn.yx.common.core.util.json.jackson.JsonUtil;
import cn.yx.common.security.pojo.JwtCacheToken;
import cn.yx.common.security.pojo.JwtUserDetails;
import cn.yx.common.security.properties.SecurityProperties;
import cn.yx.common.security.service.JwtTokenService;
import cn.yx.common.security.util.JwtUtil;
import cn.yx.common.security.webmvc.constant.message.JwtAuthenticationFilterMessageKey;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.stereotype.Component;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

/**
 * <p>Jwt认证过滤器。
 * 在UsernamePasswordAuthenticationFilter用户认证过滤器前先判断是否有jwt token传入，如果有，
 * 则从token中获取用户信息，并且检验此token是否有效，如果有效则表示此用户认证通过
 * </p>
 *
 * @author Wgssmart
 */
@Component
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    private final JwtTokenService jwtTokenService;

    private final SecurityProperties securityProperties;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtTokenService jwtTokenService, SecurityProperties securityProperties) {
        super(authenticationManager);
        this.jwtTokenService = jwtTokenService;
        this.securityProperties = securityProperties;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = getJwtToken(request);
        if (StrUtil.isNotEmpty(token)) {
            response.setContentType(ContentTypeEnum.JSON.getValue());
            response.setCharacterEncoding(CharsetEnum.UTF8.getValue());
            JwtUserDetails jwtUserDetails = JwtUtil.parseJwtUserDetails(token);
            if (Objects.isNull(jwtUserDetails)) {
                response.getWriter().write(JsonUtil.toJsonStr(
                        ResponseVo.authenticationError(JwtAuthenticationFilterMessageKey.TOKEN_ERROR)));
                return;
            }
            // 由token解析出JwtUserDetails
            JwtCacheToken jwtCacheToken = jwtTokenService.getCachedToken(jwtUserDetails);
            if (Objects.isNull(jwtCacheToken)) {
                response.getWriter().write(JsonUtil.toJsonStr(
                        ResponseVo.authenticationError(JwtAuthenticationFilterMessageKey.TOKEN_NOT_EXIST)));
                return;
            }
            String tokenInRedis = jwtCacheToken.getToken();
            if (!StrUtil.equals(token, tokenInRedis)) {
                response.getWriter().write(JsonUtil.toJsonStr(
                        ResponseVo.authenticationError(JwtAuthenticationFilterMessageKey.TOKEN_ERROR)));
                return;
            }
            // 如果redis中存在此token
            if (Objects.isNull(SecurityContextHolder.getContext().getAuthentication())) {
                // spring security上下文中不存在认证信息，则存储新的authentication到上下文
                saveAuthentication(request, jwtUserDetails);
            }
            // 只要jwt token正常使用了，就刷新缓存失效时间，注意jwt token本身的过期时间并未变更
            jwtTokenService.refreshTokenExpiration(jwtUserDetails);
        }
        filterChain.doFilter(request, response);
    }

    private String getJwtToken(HttpServletRequest request) {
        String token = request.getHeader(securityProperties.getJwtToken().getHeader());
        if (StrUtil.isEmpty(token) || !token.startsWith(securityProperties.getJwtToken().getHeaderPrefix())) {
            return null;
        }
        return token.replace(securityProperties.getJwtToken().getHeaderPrefix(), StringUtil.EMPTY_STRING);
    }

    private void saveAuthentication(HttpServletRequest request, JwtUserDetails jwtUserDetails) {
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(jwtUserDetails,
                null, jwtUserDetails.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        // 将认证状态存入SpringSecurity上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}